/**
 * DiversityTissueDriver
 * 
 * @author Rumen Kostadinov
 */

package sim;

import java.io.*;
import java.text.*;
import java.text.DateFormat;
import java.util.Date;
import java.util.*;

public class DiversityTissueDriver {
    private Scheduler scheduler;
    private TissueEnvironmentSettings params;
    
    public DiversityTissueDriver(String paramFile){

	params = new TissueEnvironmentSettings();

	int gridWidth = 300;
	int gridHeight = 300;
	int randomSeed = 0;
	int initialRow, initialColumn, xClipStart,  xClipStop,  yClipStart,  yClipStop;
	initialRow= initialColumn= xClipStart=  xClipStop=  yClipStart=  yClipStop = 0;
	double rowCryptFrequency, colCryptFrequency,  randomDensity;
	// Java-based initialization of variables
	rowCryptFrequency= colCryptFrequency=  randomDensity = -1;


	try
	    {
		// Read params from File
		FileReader fr = new FileReader(paramFile);
		BufferedReader br = new BufferedReader(fr);
		String s;
		
		// Simulation output directory
		// e.g. K=1_P=0_MU=0.00001_NU=0.00001_RSM=1_B=0.02_D=0.016_NN=100_T=20_H=300_W=300
		s = br.readLine();
		params.setDirectory(s);		

		// Simulation random seed 0-2,000,000,000
		s = br.readLine();		
		randomSeed = Integer.parseInt(s);

		// Simulation replicate number
		s = br.readLine();		
		params.setLabel("rep"+Integer.parseInt(s));

		// Grid size
		s = br.readLine();
		gridWidth = Integer.parseInt(s);
		s = br.readLine();
		gridHeight = Integer.parseInt(s);
		
		// Neutral Loci total number
		s = br.readLine();
		int neutralLociCount = Integer.parseInt(s);

		// Selective loci total number
		s = br.readLine();
		int selectiveLociCount = Integer.parseInt(s);

		// Number of neutral loci that are microsatellites
		// from 0 to n - microsat loci, 
		// from n+1 to m - selective loci
		params.setLociTypes(neutralLociCount+selectiveLociCount,0,neutralLociCount);	


		// Set the crypt division probability
		s = br.readLine();
		params.setCryptDivisionProbability(Double.valueOf(s));

		// Set the mutation model to replication-dependent k=1 or replication-independent k=0
		// k can be a double.
		s = br.readLine();
		params.setReplicationDependenceFactor(Double.valueOf(s));		

		// Rate to crypt division: basal, fastest, slowest
		s = br.readLine();
		params.setBasalCryptDivisionRate(Double.valueOf(s));
		s = br.readLine();
		params.setFastestCryptDivisionRate(Double.valueOf(s));
		s = br.readLine();
		params.setSlowestCryptDivisionRate(Double.valueOf(s));
		// Rate to crypt death: basal, fastest, slowest
		s = br.readLine();
		params.setBasalCryptDeathRate(Double.valueOf(s));
		s = br.readLine();
		params.setFastestCryptDeathRate(Double.valueOf(s));
		s = br.readLine();
		params.setSlowestCryptDeathRate(Double.valueOf(s));
		// When we set neutral and selective mutation rate
		// it is on a per-locus basis
		// Therefore, we need to know the number of neutral and selective loci
		// before we set the mutation rate
		// e.g. mean time to mutation is 10^6
		// the mutation rate is  1/(10^6/6 loci)
		// Rate to crypt neutral mutation: basal, fastest, slowest
		s = br.readLine();
		params.setBasalCryptNeutralMutationRate(Double.valueOf(s), neutralLociCount);
		s = br.readLine();
		params.setFastestCryptNeutralMutationRate(Double.valueOf(s), neutralLociCount);
		s = br.readLine();
		params.setSlowestCryptNeutralMutationRate(Double.valueOf(s), neutralLociCount);
		// Rate to crypt selective mutation: basal, fastest, slowest
		s = br.readLine();
		params.setBasalCryptSelectiveMutationRate(Double.valueOf(s), selectiveLociCount);
		s = br.readLine();
		params.setFastestCryptSelectiveMutationRate(Double.valueOf(s), selectiveLociCount);
		s = br.readLine();
		params.setSlowestCryptSelectiveMutationRate(Double.valueOf(s), selectiveLociCount);

		params.initializeLociSelectiveCoefficientsArray(neutralLociCount+selectiveLociCount);

		// Load the sel. coeff. of all sel. loci
		// On three lines         locus 0    locus 1    locus 2
		// Line 1 reproduction      0           1          2
		// Line 2 survival          0           1         -0.5
		// Line 3 mutation          0           0          0
		int j;
		for(int i=0; i<=2; i++){
		    s = br.readLine();
		    StringTokenizer st = new StringTokenizer(s);
		    // 0 to n-1 are neutral loci, n to to m-1 are selective loci
		    j=neutralLociCount;
		    while (st.hasMoreTokens()) {
			// Type is 1 through 3 (repro, surv, mut), 
			// locus is j (0 through how many selective coefficients are on the line=matches no. of loci), sel coeff at locus j
			params.setLociSelectiveCoefficients(i, j, Double.parseDouble(st.nextToken()));		    
			j++;
		    }		
		}
		
		// Compress loci to remove loci with selective coefficient s=0.0
		params.compressSelectiveLoci();
		
		// Parameter controlling whether grid will be output at days or generations intervals
		// 0 - measured in days
		// 1 - measured in generations
		s = br.readLine();
		
                if(Integer.parseInt(s)==0){

		    // Simulation days at which the grid and clone frequencies will be dumped
		    s = br.readLine();	       
		    params.setFixationIntervalTime(Double.parseDouble(s));
		    System.out.println("clone freqs interval = "+Double.parseDouble(s));
		    // Simulation days at which the clone phylogeny will be cleaned up of extinct clones
		    s = br.readLine();
		    params.setCleanupTime(Double.parseDouble(s));
		    System.out.println("cleanup = "+Double.parseDouble(s));
		    
		    // The number of days the simulation will run for
		    s = br.readLine();	       
		    params.setMaxSimulationDays(Double.parseDouble(s));
		    System.out.println("simulation days = "+Double.parseDouble(s));

		} else {
		    // Simulation generations at which the grid and clone frequencies will be dumped
		    // where one generation is 1000 crypt divisions if total population of crypts is N = 1000
		    s = br.readLine();	       
		    params.setFixationIntervalTimeGenerations(Double.parseDouble(s));
		    System.out.println("gens clone freqs interval = "+Double.parseDouble(s));
		    // Simulation generations at which the clone phylogeny will be cleaned up of extinct clones
		    s = br.readLine();
		    params.setCleanupIntervalTimeGenerations(Double.parseDouble(s));
		    
		    // The number of generations the simulation will run for
		    s = br.readLine();	       
		    params.setMaxSimulationGenerations(Double.parseDouble(s));
		}

	        // Phylogeny cutoff			     
		s = br.readLine();
		params.setPhylogenyFrequencyCutoff(Double.valueOf(s));
		
		// Seeding params
		s = br.readLine();
		initialRow = Integer.parseInt(s);
		s = br.readLine();
		initialColumn = Integer.parseInt(s);
		s = br.readLine();
		xClipStart = Integer.parseInt(s);
		s = br.readLine();
                xClipStop = Integer.parseInt(s);
		s = br.readLine();
                yClipStart = Integer.parseInt(s);
		s = br.readLine();
                yClipStop = Integer.parseInt(s);
		s = br.readLine();
	        rowCryptFrequency = Double.parseDouble(s);
		s = br.readLine();
		colCryptFrequency = Double.parseDouble(s);
		s = br.readLine();
                randomDensity = Double.parseDouble(s);
		//For starting with a full 300x300 grid use:
		// 0 0 0 300 0 300 1.0 1.0 0.0

		// Set crypt division mode
		// Apr 5 2012
		// Set 2 modes for crypt division
		// Real = only the immediate 6 neighbor hexes
		// Space = dispersal into random 6 hexes , r distance away
		s = br.readLine();
                Integer dispersal = Integer.parseInt(s);
		if(dispersal==0){
		    params.setCryptMode(TissueEnvironmentSettings.CryptMode.Real);
		    params.setDispersalRadius(0);
		} else {
		    params.setCryptMode(TissueEnvironmentSettings.CryptMode.Space);
		    params.setDispersalRadius(dispersal.intValue());
		    System.out.println( "Space crypt mode");
		}

		// Close input file
		fr.close();
	    }
	catch (IOException ex){
	    System.out.println( "Exception caught in reading input file: " + ex.getMessage());
	    ex.printStackTrace(System.out);		
	}

	// Prepare parameters of the simulation
	try{
	    PHexGrid grid = new PHexGrid(gridWidth, gridHeight, CryptFactory.getFactory(), params, true, false,initialRow, initialColumn, rowCryptFrequency, colCryptFrequency, xClipStart, xClipStop,yClipStart,yClipStop,randomDensity,randomSeed);
	    scheduler = new Scheduler(grid, randomSeed, params);
	    // Write parameters to file
	    writeSimulationParameters(paramFile);
	}
	catch( Exception ex ){
	    System.out.println( "Exception caught in DiversityTissueDriver:GuiTissueDriver(): " + ex.getMessage());
	    ex.printStackTrace(System.out);
	}		
	
	// Run the simulation
	Scheduler.SimulationTerminationReason eTermReason = scheduler.RunSimulation();
	switch(eTermReason){
	case DominatedByDeath:
	    System.out.println("Tissue death");
	    break;
	case MaxTimeReached:
	    System.out.println("End of simulation reached");
	    try {
		PrintWriter output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".out",true) ) );
		DateFormat fmt = DateFormat.getInstance();
		output.println("stop " + fmt.format(new Date()));
		output.close();
	    }
	    catch (IOException ex){
		System.out.println( "Exception caught in .out " + ex.getMessage());
		ex.printStackTrace(System.out);		
	    }	    	    
	    break;
	}
    }
    
    public void writeSimulationParameters(String paramFile) throws IOException{

	File f;
	boolean success;

	// Create directory for output
	success = (new File(params.getDirectory())).mkdir();
	
	// Delete old files associated with the run # random sweep in current directory
	f = new File(params.getDirectory()+"/"+params.getLabel()+".in");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".out");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".experiments");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".crypts");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".loci");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".log");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".frequencies");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".clones");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".patterns");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".phylogeny.log");
	success = f.delete();
	f = new File(params.getDirectory()+"/"+params.getLabel()+".clones.number.log");
	success = f.delete();

	NumberFormat decForm = new DecimalFormat("#0.00000000");
	// Output "experiments" table fields
        PrintWriter output = null;
	/*
	try {
	    output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".experiments",true) ) );
	    StringBuffer sb = new StringBuffer();
	    sb.append("NULL,").append(params.getMaxSimulationDays()+",")
		.append(decForm.format(params.getBasalCryptDivisionRate())+",")
		.append(decForm.format(params.getBasalCryptDeathRate())+",")
		.append(decForm.format(params.getBasalCryptNeutralMutationRate())+",")
		.append(decForm.format(params.getBasalCryptSelectiveMutationRate()));
	    output.println(sb.toString());
	}
	finally{
	    if (output != null) output.close();
	}
	*/
	// output "loci" table
	output = null;
	try {
	    output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".loci",true) ) );
	    Double[][] loci = params.getLoci();
	    for(int i=0; i<loci.length; i++){
		StringBuffer sb = new StringBuffer();
		sb.append(i+",")
		    .append(params.getLocusType(i)+",")
		    .append(decForm.format(loci[i][0])+",")
		    .append(decForm.format(loci[i][1])+",")
		    .append(decForm.format(loci[i][2]));
		output.println(sb.toString());
	    }
	}
	finally{
	    if (output != null) output.close();
	}
    
	// Go through all crypts, output "crypts" table
	decForm = new DecimalFormat("#0.000");
	output = null;
        try {
            output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".crypts",true) ) );	    
	    // foreach crypt dump which clone_id it contains
	    int numCrypts = scheduler.grid.getSize();
	    for (int i=0; i<numCrypts; i++) {
		// Get the current crypt
		Crypt crypt = scheduler.grid.getCrypt(i);
		StringBuffer sb = new StringBuffer();
		sb.append(i+",")
		    .append(decForm.format(crypt.getX())+",")
		    .append(decForm.format(crypt.getY()));
		output.println(sb.toString());	    
	    }
	}
	finally{
            if (output != null) output.close();
        }

	// Prepare simulation run output .out in simulation run folder 
	try {
	    output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".out",true) ) );
	    output.println("# Agent-based model of crypt evolutionary dynamics");
	    output.println("# This code comes with ABSOLUTELY NO WARRANTY");
	    output.println("# This is free software, and you are welcome to redistribute it under certain conditions");
	    output.println("# It has been licensed under the GNU General Public License");
	    output.println("# See http://www.gnu.org/licenses/licenses.html#GPL for details");
	    output.println();
	    DateFormat fmt = DateFormat.getInstance();
	    output.println("start " + fmt.format(new Date()));
	}
	finally{
	    if (output != null) output.close();
	}	

	// Save the initial parameters in the simulation run folder
	try{
	    FileReader fr = new FileReader(paramFile);
	    BufferedReader br = new BufferedReader(fr);
	    String line;
	    output = new PrintWriter( new BufferedWriter( new FileWriter(params.getDirectory()+"/"+params.getLabel()+".in",true) ) );
	    while ((line = br.readLine()) != null) {
		output.println(line);
	    } 
	}
	finally{
	    if (output != null) output.close();
	}
    }


    /**
     * Main model execution function
     * @param args Input file containing the parameters of the simulation run
     */
    public static void main(String[] args) {
	DiversityTissueDriver d = new DiversityTissueDriver(args[0]);
    }

}
